/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package spaceinvaders.sistema;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.util.Random;
import javax.swing.JPanel;
import spaceinvaders.personagens.PersonagemFactory;
import spaceinvaders.personagens.PersonagemFactory.TipoPersonagem;
import spaceinvaders.personagens.Personagem;
import spaceinvaders.extra.EfeitosSonoros;
import spaceinvaders.extra.EfeitosSonoros.TipoEfeitosSonoros;
import spaceinvaders.extra.Music;
import spaceinvaders.util.Commons;
import spaceinvaders.util.Timer;
import spaceinvaders.util.TimerListener;

/**
 * Classe que gerencia todos os elementos do jogo.
 * @author Danilo Augusto Silva
 */
public class Sistema extends JPanel implements TimerListener, Commons {

    private Timer timer; //Variavel para controlar a thread
    private Personagem[] pers; //Vetor de personagens, contem o canhao na posicao 0 e os 55 invasores
    private Personagem invaderBonus; //objeto personagem que irá armazenar o invasor que dá bonus na pontuacao
    private Personagem[] shoot; //objeto tiro, optei por faze-lo como extencao de personagem, ainda nao sei se é uma boa ideia...
    private Personagem[] explosion; //objeto explosao, optei por faze-lo como extencao de personagem
    private int[] EXPLOSION_REPAINT; //vetor de inteiros, para auxiliar o tempo que uma explosao ocorre
    private Point[] posicao = new Point[55]; //vetor auxiliar que armazena a posicao dos 55 invasores
    private Point cannonposicao; //variavel auxiliar para armazenar a posicao do canhao
    private Random random = new Random(); //variavel random para chamar a nave bonus a partir de um critério randomico

    //Variaveis para os efeitos sonoros e musica de fundo
    private EfeitosSonoros ef = new EfeitosSonoros();
    private Music ms = new Music();


    private int invadersMovement = 1; //Sentido de movimentacao dos invasores (1 = direita, -1 = esquerda)
    private int InvaderBonusMovement = -invadersMovement; //sentido de movimentacao do invasor bonus, contrario dos invasores normais

    private int invaderSpeed = 1; //velocidade dos invasores, comecando em 1...
    private int score = 0; //pontuacao
    private int invadersCountdown = 55; //variavel para armazenar quantos invasores faltam matar
    private int shootNumber = 15; //numero maximo de tiros na tela
    private int shootNumberRemember = shootNumber;
    private int explosionNumber = shootNumber; //numero maximo de explosoes na tela
    private boolean isShooting = false; //booleano para saber se o usuario apertou o botao de tiro
    private boolean isExploding = false; //booleano para saber se alguma nave explodiu
    private Point explosionPosition; //posicao da explosao
    private int shootDelay = 0; //variavel auxiliar para o delay de tiros (evitar metralhadora)
    private boolean doBonus = false; //aponta ao paintComponent que a nave bonus deve ser feita
    private boolean perdeu = false; //avisa quando perde

    //private int vida = 3;

    private int level = 1;


    /**
     * Construtor da classe Sistema, cria os vetores de personagens, explosao,
     * tiros, seleciona cor do plano de fundo e inicia musica de fundo.
     */

    public Sistema() {
        ms.playMusic(Music.TipoMusica.STARWOLF);
        this.setBackground(Color.BLACK);
        this.timer = new Timer(this, DELAY);
        this.pers = new Personagem[INVADERS_NUMBER + 1];
        this.shoot = new Personagem[shootNumber + 1];
        this.explosion = new Personagem[explosionNumber + 1];
        this.EXPLOSION_REPAINT = new int[explosionNumber + 1];
    }

    /**
     * Metodo que inicializa o sistema, iniciando o timer (thread) e chamando o
     * método de inicializacao de jogo.
     */

    public void inicializar() {
        timer.start();
        this.begingame();
    }

    /**
     * Método que inicializa o jogo, criando os personagens canhao e invasores,
     * pegando as posicoes e ajustando em um vetor de posicoes para facilitar
     * as modificacoes.
     */

    public void begingame() {
        pers[0] = PersonagemFactory.getInstance(this, TipoPersonagem.CANNON);
        cannonposicao = pers[0].getPosicao();

        for (int i = 1; i < 12; i++) {
            pers[i] = PersonagemFactory.getInstance(this, TipoPersonagem.INVADER_TOP);
            posicao[i - 1] = pers[i].getPosicao();
        }
        for (int i = 12; i < 34; i++) {
            pers[i] = PersonagemFactory.getInstance(this, TipoPersonagem.INVADER_MID);
            posicao[i - 1] = pers[i].getPosicao();
        }
        for (int i = 34; i < pers.length; i++) {
            pers[i] = PersonagemFactory.getInstance(this, TipoPersonagem.INVADER_BOT);
            posicao[i - 1] = pers[i].getPosicao();
        }

        //Tiro e explosao ficaram como tipo Personagem... ainda nao sei se é o melhor caminho...
        for (int i = 0; i < shoot.length; i++) {
            shoot[i] = null;
        }

        for (int i = 0; i < explosion.length; i++) {
            explosion[i] = null;
            EXPLOSION_REPAINT[i] = 0;
        }

        invaderBonus = null;
        shootNumber = shootNumberRemember;

        initialStageInvaders();


    }

    /**
     * Método que inicia a posicao dos invasores (tecnicamente, a matriz
     * inicial).
     */

    private void initialStageInvaders() {
        for (int i = 0; i < 11; i++) {
            posicao[i].x = posicao[i].x + 40 * i;
        }
        for (int i = 11; i < 22; i++) {
            posicao[i].x = posicao[i].x + 40 * (i - 11);
        }
        for (int i = 22; i < 33; i++) {
            posicao[i].y = posicao[i].y + 30;
            posicao[i].x = posicao[i].x + 40 * (i - 22);
        }
        for (int i = 33; i < 44; i++) {
            posicao[i].x = posicao[i].x + 40 * (i - 33);
        }
        for (int i = 44; i < 55; i++) {
            posicao[i].y = posicao[i].y + 30;
            posicao[i].x = posicao[i].x + 40 * (i - 44);
        }
    }

    /**
     * Método paintComponent para desenho no painel. A cada atualizacao
     * (via thread pelo metodo update), este metodo é chamado.
     * @param g do tipo Graphics, parametro para desenho.
     */

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D) g;

        g2.setColor(Color.DARK_GRAY);
        g2.fill3DRect(0, 540, 800, 600, true);

        Font f = new Font(Font.SANS_SERIF, Font.BOLD, 12);

        g2.setFont(f);
        g2.setColor(Color.white);

        g2.drawString("Level: " + level, 0, 570);

        //Desenha Personagens (Canhao e invaders)
        pers[0].desenha(g2);
        for (int i = 1; i < pers.length; i++) {
            if (pers[i] != null) {
                pers[i].desenha(g2);
            }
        }


        //Caso necessario, fazer o tiro (iniciar objeto shoot caso nao exista)
        this.doShoot(g2);

        //Caso necessario, fazer a explosao (iniciar objeto explosion caso nao exista)
        this.doExplosion(g2);

        //Caso necessario, fazer invader Bonus
        this.doBonus(g2);

        g.setFont(f);
        g.setColor(Color.white);

        g.drawString("SCORE: " + score, 0, 12);
        g.drawString("Invaders Left: " + invadersCountdown, 200, 12);

        //se pausou o jogo:
        if (Comandos.isPaused()) {
            if (Comandos.isPauseSound()) {
                ms.stopMusic();
                ef.playSoundEffect(TipoEfeitosSonoros.PAUSED);
                Comandos.setPauseSound(false);
            }
            Font bf = new Font(Font.SANS_SERIF, Font.BOLD, 20);
            g.setFont(bf);
            g.drawString("PAUSED", 250, 300);
        }

    }

    /**
     * Método que, caso necessario, faz o tiro (iniciar objeto shoot caso nao
     * exista). Serve de auxiliar ao paintComponent.
     * @param g2 do tipo Graphics2D, para desenho.
     */

    private void doShoot(Graphics2D g2) {
        if (isShooting) {
            ef.playSoundEffect(TipoEfeitosSonoros.LASER);
            int shootNum = 0;
            while (shoot[shootNum] != null) {
                shootNum++;
            }

            shoot[shootNum] = PersonagemFactory.getInstance(this,
                    TipoPersonagem.SHOOT);
            shootNumber--;

            shoot[shootNum].setPosicaoShoot(cannonposicao);
            isShooting = false;
        }

        //Verifica se é pra desenhar o tiro
        for (int i = 0; i < shoot.length; i++) {
            if (shoot[i] != null) {
                shoot[i].desenha(g2);
            }
        }
    }

    /**
     * Método que, caso necessario, faz a explosao (inicia objeto explosion caso
     * nao exista). Serve de auxiliar ao paintComponent.
     * @param g2 do tipo Graphics2D, para desenho.
     */

    private void doExplosion(Graphics2D g2) {
        if (isExploding) {
            ef.playSoundEffect(TipoEfeitosSonoros.EXPLOSION);
            int explosionNum = 0;
            while (explosion[explosionNum] != null) {
                explosionNum++;
            }
            explosion[explosionNum] = PersonagemFactory.getInstance(this, TipoPersonagem.EXPLOSION);
            explosion[explosionNum].setPosicaoExplosion(explosionPosition);
            isExploding = false;
            EXPLOSION_REPAINT[explosionNum] = EXPLOSION_TIME;
        }

        //Verifica se é pra desenhar a explosao
        for (int i = 0; i < explosion.length; i++) {
            if (explosion[i] != null) {
                if (EXPLOSION_REPAINT[i] >= 0) {
                    explosion[i].desenha(g2);
                    EXPLOSION_REPAINT[i]--;
                }
            }
        }
    }

    /**
     * Método que, caso a variavel doBonus seja verdadeiro, faz o invasor bonus.
     * @param g2 do tipo Graphics2D, para desenho.
     */

    private void doBonus(Graphics2D g2) {

        if (doBonus) {
            doBonus = false;
            invaderBonus = PersonagemFactory.getInstance(this, TipoPersonagem.INVADER_BONUS);
            if (invadersMovement > 0) {
                invaderBonus.getPosicao().x = 580;
            }
            InvaderBonusMovement = -invadersMovement;
        }
        if (invaderBonus != null) {
            invaderBonus.desenha(g2);
        }
    }

    /**
     * Método update. Todas as atualizacoes do jogo sao feitas por este metodo,
     * indo desde a movimentacao do canhao, dos invasores, do tiro, verificacao
     * se um tiro acertou algum invasor, soma de pontos, etc.
     */

    public void update() {

        //Caso isPaused for false, quer dizer que acabou de despausar, logo, recomeca musica.
        if (!Comandos.isPaused()) {
            if (!Comandos.isPauseSound()) {
                ef.playSoundEffect(TipoEfeitosSonoros.PAUSED);
                ms.playMusic(Music.TipoMusica.STARWOLF);
                Comandos.setPauseSound(true);
            }

            //Movimentacao

            /*     if (Comandos.isUpKey()) {
            cannonposicao.y = cannonposicao.y - MOVEMENT_INCR;
            }
            if (Comandos.isDownKey()) {
            cannonposicao.y = cannonposicao.y + MOVEMENT_INCR;
            }*/
            if (Comandos.isLeftKey() && (cannonposicao.x > 0)) {
                cannonposicao.x = cannonposicao.x - MOVEMENT_INCR;
            }
            if (Comandos.isRightKey() && (cannonposicao.x < 560)) {
                cannonposicao.x = cannonposicao.x + MOVEMENT_INCR;
            }

            //Tiro
            if (Comandos.isFireKey()) {
                if (shootDelay <= 0) { //Se o delay é menor ou igual a zero (nao precisa esperar mais)
                    if (shootNumber >= 0) { //Se ainda há municao (decrementa municao depois de iniciar objeto em doShoot())
                        isShooting = true;
                        shootDelay = SHOOT_DELAY; // para evitar metralhadora, uso de delay para tiro
                    }
                }
            }

            shootDelay--; //Delay para o tiro, evitando metralhadora

            //Movimentacao dos tiros
            for (int i = 0; i < shoot.length; i++) {
                if (shoot[i] != null) {
                    shoot[i].getPosicao().y = shoot[i].getPosicao().y - MOVEMENT_SHOOT;

                    if (shoot[i].getPosicao().y < -30) {
                        shoot[i] = null;
                        shootNumber++;
                    }
                }
            }

            //Movimentacao dos invaders
            for (int i = 0; i < posicao.length; i++) {
                if (pers[i + 1] != null) {
                    posicao[i].x = posicao[i].x + invadersMovement * invaderSpeed;
                    if (posicao[i].x < 0 || (posicao[i].x > 570 && posicao[i].x < 1000)) {
                        invadersMovement = -invadersMovement;
                        for (int j = 0; j < posicao.length; j++) {
                            posicao[j].y = posicao[j].y + 30;
                        }
                    }
                }
            }

            //Movimentacao do invader bonus
            if (invaderBonus != null) {
                invaderBonus.getPosicao().x = invaderBonus.getPosicao().x +
                        InvaderBonusMovement * 3;
                if ((invaderBonus.getPosicao().x < -30) || (invaderBonus.getPosicao().x > 580)) {
                    invaderBonus = null;
                }
            }


            //ACERTOU INVADER (ou bonus)!
            for (int j = 0; j < shoot.length; j++) { //Para todo tiro
                for (int i = 0; i < posicao.length; i++) { //Para toda posicao de invasor
                    if (shoot[j] != null) { //verificar se tiro nao é null.
                        if (pers[i + 1] != null) { //verificar se o personagem está vivo.
                            if (shoot[j].getPosicao().x + 20 >= posicao[i].x
                                    && shoot[j].getPosicao().x <= posicao[i].x + 20
                                    && shoot[j].getPosicao().y + 20 >= posicao[i].y
                                    && shoot[j].getPosicao().y <= posicao[i].y) { //caso tenha acertado o tiro
                                invadersCountdown--;
                                explosionPosition = posicao[i];
                                isExploding = true;
                                shoot[j] = null;
                                shootNumber++;

                                score = score + pers[i + 1].score();
                                pers[i + 1] = null;
                            }
                        }
                    }

                    //Invader Bonus
                    if (shoot[j] != null) { //Caso tenha explodido algum acima o tem mais tiros apontando pra null do que atualmente, logo precisa fazer a verificacao novamente.
                        if (invaderBonus != null) {
                            if (shoot[j].getPosicao().x + 20 >= invaderBonus.getPosicao().x
                                    && shoot[j].getPosicao().x <= invaderBonus.getPosicao().x + 40
                                    && shoot[j].getPosicao().y + 30 >= invaderBonus.getPosicao().y
                                    && shoot[j].getPosicao().y <= invaderBonus.getPosicao().y) {
                                explosionPosition = invaderBonus.getPosicao();
                                isExploding = true;
                                shoot[j] = null;
                                shootNumber++;
                                score = score + invaderBonus.score();
                                invaderBonus = null;
                            }
                        }
                    }
                }
            }

            //Verificacao se é preciso fazer o repaint da explosao (pra explosao ficar aparecendo por alguns frames)
            for (int i = 0; i < EXPLOSION_REPAINT.length; i++) {
                if (EXPLOSION_REPAINT[i] <= 0) {
                    explosion[i] = null;
                }
            }




            //Se so sobraram 10 invaders, aumentar velocidade em 1
            if (invadersCountdown == 10) {
                invaderSpeed=level+1;
            }

            //fazer aparecer a nave bonus aleatoriamente
            if ((invadersCountdown > 0) && invaderBonus == null &&
                    ((random.nextInt(1000)) == 10)) {
                doBonus = true;
            }

            //Se matou todos os invasores
            if (invadersCountdown == 0) {
                ef.playSoundEffect(TipoEfeitosSonoros.VICTORY);
                //animacao de vitoria ?
                invadersCountdown = 55;

                level++;
                invaderSpeed = level;
                this.begingame();
            }

            //Verificar se invasores chegaram na base
            for (int i = 0; i<posicao.length; i++)
                if (pers[i+1] != null)
                    if(posicao[i].y >= pers[0].getPosicao().y) perdeu=true;

            if (perdeu) {
                ef.playSoundEffect(TipoEfeitosSonoros.EXPLOSION);
                invadersCountdown = 55;

                //vida--;
                invaderSpeed = level;
                perdeu = false;
                this.begingame();
            }



        }
        this.repaint();

    }
}
